package controller;

import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import model.Model;
import object.Point;
import object.PointType;

public class Controller {
	public static void generateRandom(Point[][] points){
		clearPoints(points);
		for(int x = 0; x < Model.getInstance().xSize; x++){
			for(int y = 0; y < Model.getInstance().ySize; y++){
				int rand = (int)(Math.random() * 7);
				PointType t;
				switch(rand){
				case 0:
					t = PointType.deepWater;
					break;
				case 1:
					t = PointType.desert;
					break;
				case 2:
					t = PointType.prairie;
					break;
				case 3:
					t = PointType.mountain;
					break;
				case 4:
					t = PointType.snow;
					break;
				case 5:
					t = PointType.forest;
					break;
				default:
					t = PointType.water;
					break;
				}
				points[x][y].setType(t);
			}
		}
	}

	public static void aStar(Point[][] points, ArrayList<Point> route){
		System.out.println("running me some A*");
		//THREAD START
		class ThreadAStar implements Runnable {

			Point[][] points;
			Point start;
			Point end;

			public ThreadAStar(Point[][] points, Point start, Point end) {
				this.points = points;
				this.start = start;
				this.end = end;
			}

			public void run() {
				HashMap<Point, PointInfo> openSet = new HashMap<Point, PointInfo>();
				HashMap<Point, PointInfo> closedSet = new HashMap<Point, PointInfo>(); 
				//LinkedHashMap<Point, PointInfo> path = new LinkedHashMap<Point, PointInfo>();

				//create the information for the starting point
				//using Euclidiean Distance as the heuristic, cost to get there is zero
				PointInfo hold = new PointInfo(Math.sqrt( (start.x-end.x)*(start.x-end.x) + (start.y-end.y) * (start.y-end.y) ), 0);
				openSet.put(start, hold );

				// until a path is found or a path is found to not exist
				while(!openSet.isEmpty())
				{
					//Choose to move to the area with the lowest f_score
					Map.Entry<Point, PointInfo> current = null;
					for( Map.Entry<Point, PointInfo> pair : openSet.entrySet() )
					{
						if( current == null )
							current = pair;
						if( pair.getValue().fScore < current.getValue().fScore)
							current = pair;
					}

					//remove selection from the empty set and place in closed set
					if(openSet.remove(current.getKey())==null)
						System.out.println("Warning: The current key was not removed");
					closedSet.put(current.getKey(), current.getValue());

					//check to see if we have reached destination
					if(current.getKey()==end){
						getPath(current.getKey(), closedSet);
						break;
					}

					//gather all of its neighbors
					for( int x=-1; x<2; x++ )
						for( int y=-1; y<2; y++ )
						{
							//skip the value of current
							if( x == 0 && y == 0 )
								continue;


							int tempX = current.getKey().x + x;
							int tempY = current.getKey().y + y;
							if(tempX==-1||tempY==-1)
								return;
							Point neighbor = points[tempX][tempY];

							//if we have already looked at the point, we can skip it.
							if( closedSet.containsKey(neighbor) || neighbor.getCost() == -1 )
								continue;

							double dist = Math.sqrt(  (current.getKey().x - neighbor.x )*(current.getKey().x - neighbor.x ) + (current.getKey().y - neighbor.y )*(current.getKey().y - neighbor.y ) );
							double tentGScore = current.getValue().gScore + neighbor.getCost()*dist;
							//TODO: Check to see if this statement produces null exceptions.
							if( !openSet.containsKey(neighbor) || tentGScore <= openSet.get(neighbor).gScore)
							{
								double fScore = tentGScore + Math.sqrt( (neighbor.x-end.x)*(neighbor.x-end.x) + (neighbor.y-end.y) * (neighbor.y-end.y) );
								if(!openSet.containsKey(neighbor))
								{

									hold = new PointInfo(fScore, tentGScore);
									hold.path = current.getKey();
									openSet.put(neighbor, hold);
								}
								else
								{
									openSet.get(neighbor).gScore = tentGScore;
									openSet.get(neighbor).fScore = fScore;
									openSet.get(neighbor).path = current.getKey();
								}
							}
						}


				}
			}
		}
		//THREAD END
		for(int x = 0; x < route.size()-1; x++){
			new Thread((new ThreadAStar(points, route.get(x), route.get(x+1)))).run();
			//System.out.println(System.currentTimeMillis());
		}
	}

	static void getPath(Point current, HashMap<Point, PointInfo> path) {
		current.setCurrentColor(Color.red);
		if( path.get(current).path != null )
			getPath(path.get(current).path, path );
		return;
	}

	static class PointInfo{
		//heuristic
		public double fScore;
		//current cost
		public double gScore;
		//path to get there
		public Point path = null;
		public PointInfo(double heuristicCost, double costToStart){
			fScore = heuristicCost;
			gScore = costToStart;
		}
	}



	public static void generateMap(Point[][] points) {
		System.out.println("controller.generatemap");
		clearPoints(points);
		ArrayList<Point> curPoints = new ArrayList<Point>();
		for(int r = 0; r < 12000; r++){
			int curX = (int) (Math.random()*Model.getInstance().xSize);
			int curY = (int) (Math.random()*Model.getInstance().ySize);
			Point p = new Point(curX, curY);
			p.setCurrentColor(Color.RED);
			if(isGoodPoint(p, curPoints))
			{
				curPoints.add(p);
				p.flag = true;
			}
		}
		addNeighbors(curPoints);
		setWater(curPoints);
		setLand(curPoints);

		setWaterBiomes(curPoints);
		setLandBiomes(curPoints);

		//TODO: CHANGE
		setRemaining(curPoints);

		for(Point p : curPoints){
			points[p.x][p.y]=p;
		}

		fillMap(points, curPoints);


	}
	private static void fillMap( Point[][] points, ArrayList<Point> curPoints) {
		/*int limit = 30;
		int xLimit = Model.getInstance().xSize - limit;
		int yLimit = Model.getInstance().ySize - limit;
		int xSize = Model.getInstance().xSize;
		int ySize = Model.getInstance().ySize;
		double distance;
		double smallestDistance;
		Point closest;
		for(int x = 0; x < xSize; x++){
			for(int y = 0; y < ySize; y++){
				if( x < 30 || x > xLimit || y < 30 || y > yLimit )
					points[x][y].setType(PointType.deepWater);
				if(points[x][y].getType() != null)
					continue;
				closest = curPoints.get(0);
				smallestDistance = Double.MAX_VALUE;
				for(Point p : curPoints){
					distance = Math.abs(p.x-x)+Math.abs(p.y-y);
					if(smallestDistance > distance){
						closest = p;
						smallestDistance = distance;
					}
				}
				points[x][y].setType(closest.getType());
			}
		}*/
		int limit = 30;
		int xLimit = Model.getInstance().xSize - limit;
		int yLimit = Model.getInstance().ySize - limit;

		for(int x = 0; x < Model.getInstance().xSize; x++){
			for(int y = 0; y < Model.getInstance().ySize; y++){
				if( x < 30 || x > xLimit || y < 30 || y > yLimit )
					points[x][y].setType(PointType.deepWater);
				if(points[x][y].getType() != null)
					continue;
				for(int distance = 0; ; distance++){
					//number to check distance*2+1
					int thisY = y+distance;
					int thisX = x+distance;
					for(; thisY >= y-distance; thisY--){
						if(points[thisX][thisY].flag==true)
							points[x][y].setType(points[thisX][thisY].getType());
					}
					for(; thisX >= x-distance; thisX--){
						if(points[thisX][thisY].flag==true)
							points[x][y].setType(points[thisX][thisY].getType());
					}
					for(; thisY <= y+distance; thisY++){
						if(points[thisX][thisY].flag==true)
							points[x][y].setType(points[thisX][thisY].getType());
					}
					for(; thisX <= x+distance; thisX++){
						if(points[thisX][thisY].flag==true)
							points[x][y].setType(points[thisX][thisY].getType());
					}

					if(points[x][y].getType() != null)
						break;
				}
			}
		}
	}

	//TODO: change to better
	private static void setRemaining(ArrayList<Point> curPoints) {
		for(Point p : curPoints)
			if(p.getType() == null || p.getType() == PointType.temp)
				p.setType(PointType.prairie);
	}

	private static void setWaterBiomes(ArrayList<Point> curPoints) {
		for(Point p : curPoints){
			if(p.getType() == null){
				for(Point n : p.neighbors){
					if(n.getType() == PointType.temp){
						p.setType(PointType.water);
						break;
					}
				}
				if(p.getType() == null){//still null
					if(Math.random() > .2){
						p.setType(PointType.deepWater);
					} else if(Math.random() > .5){
						p.setType(PointType.water);
					} else {
						p.setType(PointType.water);
						for(Point n : p.neighbors){
							if(Math.random() > .5){
								n.setType(PointType.water);
							}
						}
					}
				}
			}
		}
	}

	private static void setLandBiomes(ArrayList<Point> curPoints) {
		//gather an arraylist of Points of Type 'temp'
		ArrayList<Point> land = new ArrayList<Point>();
		for( Point p : curPoints )
			if( p.getType() == PointType.temp )
				land.add(p);

		addType(land, PointType.water, 1.0, 1);
		System.out.println("Added lakes");
		addType(land, PointType.mountain, 1.6, 1.4);
		addType(land, PointType.desert, 1.8, 2.4);
		addType(land, PointType.forest, 1.9, 1.9);
		addType(land, PointType.forest, 12, .6);

	}



	private static void addType( ArrayList<Point> land, PointType type, double probStart, double probCont) {
		Point p;
		while( Math.random() < probStart )
		{
			//System.out.println("Placing the first point");
			p = land.get( (int)(Math.random()*land.size()));
			if(type == PointType.mountain)
				p.setType(PointType.snow);
			addMore( land, p, type, probCont);
			probStart /= 1.5;
		}

	}



	private static void addMore(ArrayList<Point> land, Point p, PointType type, double probCont) {
		if(p.getType() == PointType.temp){
			if(!(type == PointType.forest && neighborIs(p, PointType.desert))){
				if(type == PointType.mountain && probCont >= .5)
					p.setType(PointType.snow);
				else
					p.setType(type);
			}
		}
		land.remove(p);
		for( Point a : p.neighbors )
		{
			if( Math.random() < probCont && a.getType() == PointType.temp ){
				addMore(land, a, type, probCont/2 );
			}
		}
	}

	//Will create a center land mass which we will build off of
	private static void setLand(ArrayList<Point> curPoints) {
		double xProp = .78;
		double yProp = .58;

		int xRangeSize = (int)(Model.getInstance().xSize * xProp );
		int xLower = (int)(Model.getInstance().xSize * ((1 - xProp)/2) );
		int xUpper = xLower + xRangeSize;

		int yRangeSize = (int)(Model.getInstance().ySize * yProp );
		int yLower = (int)(Model.getInstance().ySize * ((1 - yProp)/2) );
		int yUpper = yLower + yRangeSize;


		for( int x=0; x < (int)((Model.getInstance().xSize * Model.getInstance().ySize) / 45000); x++ )
		{

			Point p;
			do{
				int point = (int)(Math.random()*curPoints.size());
				p = curPoints.get(point);
			} while( p.getType() == PointType.deepWater || p.x < xLower || p.x > xUpper || p.y < yLower || p.y > yUpper);

			p.setType(PointType.temp);

			double radius = 10 + Math.random()*120;

			for( Point a : curPoints )
			{
				double distance = Math.sqrt((a.x-p.x)*((a.x-p.x))+(a.y-p.y)*((a.y-p.y)));
				if( distance < radius + Math.random()*55 && a.getType() == null )
					a.setType(PointType.temp);
			}

		}

	}

	//Sets the outer edge to water so we generate an island
	private static void setWater(ArrayList<Point> curPoints) {
		int limit = 30 + (int)(Math.random()*30);
		int xLimit = Model.getInstance().xSize - limit;
		int yLimit = Model.getInstance().ySize - limit;
		for( Point p: curPoints )
		{
			if( p.x < 30 || p.x > xLimit || p.y < 30 || p.y > yLimit )
				p.setType(PointType.deepWater);
			else if( (p.x < 60 || p.x > xLimit-30 || p.y < 60 || p.y > yLimit-30) && Math.random() > .3)
				p.setType(PointType.deepWater);
		}
	}

	public static void clearPoints(Point[][] points) {
		for(int x = 0; x < Model.getInstance().xSize; x++){
			for(int y = 0; y < Model.getInstance().ySize; y++){
				points[x][y].neighbors.clear();
				points[x][y] = new Point(x, y);
			}
		}
	}
	private static boolean isGoodPoint(Point check, ArrayList<Point> curPoints){
		if(Math.random() <= .0025)
			return true;
		for(Point p : curPoints){
			if(Math.sqrt((check.x-p.x)*((check.x-p.x))+(check.y-p.y)*((check.y-p.y))) < 15){
				return false;
			}
		}
		//System.out.println("good point");
		return true;
	}
	private static void addNeighbors(ArrayList<Point> curPoints){
		for( Point p : curPoints )
			for( int x=0; x < curPoints.size(); x++)
			{
				if(Math.sqrt((curPoints.get(x).x-p.x)*((curPoints.get(x).x-p.x))+(curPoints.get(x).y-p.y)*((curPoints.get(x).y-p.y))) < 35){
					p.neighbors.add(curPoints.get(x));
					//System.out.println("Neighbor added");
				}
			}
	}
	private static boolean neighborIs(Point p, PointType type){
		for(Point n : p.neighbors){
			if(n.getType() == type)
				return true;
		}
		return false;
	}

	public static void resetPoints(Point[][] points) {
		for(int x = 0; x < Model.getInstance().xSize; x++){
			for(int y = 0; y < Model.getInstance().ySize; y++){
				points[x][y].resetColor();
			}
		}
	}

}
